﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.Serialization.Metadata
{
    /// <summary>
    /// Contains utilities and combinators acting on <see cref="IJsonTypeInfoResolver"/>.
    /// </summary>
    public static class JsonTypeInfoResolver
    {
        /// <summary>
        /// Combines multiple <see cref="IJsonTypeInfoResolver"/> sources into one.
        /// </summary>
        /// <param name="resolvers">Sequence of contract resolvers to be queried for metadata.</param>
        /// <returns>A <see cref="IJsonTypeInfoResolver"/> combining results from <paramref name="resolvers"/>.</returns>
        /// <exception cref="ArgumentException"><paramref name="resolvers"/> is null.</exception>
        /// <remarks>
        /// The combined resolver will query each of <paramref name="resolvers"/> in the specified order,
        /// returning the first result that is non-null. If all <paramref name="resolvers"/> return null,
        /// then the combined resolver will also return <see langword="null"/>.
        ///
        /// Can be used to combine multiple <see cref="JsonSerializerContext"/> sources,
        /// which typically define contract metadata for small subsets of types.
        /// It can also be used to fall back to <see cref="DefaultJsonTypeInfoResolver"/> wherever necessary.
        /// </remarks>
        public static IJsonTypeInfoResolver Combine(params IJsonTypeInfoResolver?[] resolvers)
        {
            if (resolvers is null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(resolvers));
            }

            var resolverChain = new JsonTypeInfoResolverChain();
            foreach (IJsonTypeInfoResolver? resolver in resolvers)
            {
                resolverChain.AddFlattened(resolver);
            }

            return resolverChain.Count == 1 ? resolverChain[0] : resolverChain;
        }

        /// <summary>
        /// Creates a resolver applies modifications to the metadata generated by the source <paramref name="resolver"/>.
        /// </summary>
        /// <param name="resolver">The source resolver generating <see cref="JsonTypeInfo"/> metadata.</param>
        /// <param name="modifier">The delegate modifying non-null <see cref="JsonTypeInfo"/> results.</param>
        /// <returns>A new <see cref="IJsonTypeInfoResolver"/> instance applying the modifications.</returns>
        /// <remarks>
        /// This method is closely related to <see cref="DefaultJsonTypeInfoResolver.Modifiers"/> property
        /// extended to arbitrary <see cref="IJsonTypeInfoResolver"/> instances.
        /// </remarks>
        public static IJsonTypeInfoResolver WithAddedModifier(this IJsonTypeInfoResolver resolver, Action<JsonTypeInfo> modifier)
        {
            if (resolver is null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(resolver));
            }
            if (modifier is null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(modifier));
            }

            return resolver is JsonTypeInfoResolverWithAddedModifiers resolverWithModifiers
                ? resolverWithModifiers.WithAddedModifier(modifier)
                : new JsonTypeInfoResolverWithAddedModifiers(resolver, new[] { modifier });
        }

        /// <summary>
        /// Gets a resolver that returns null <see cref="JsonTypeInfo"/> for every type.
        /// </summary>
        internal static IJsonTypeInfoResolver Empty { get; } = new EmptyJsonTypeInfoResolver();

        /// <summary>
        /// Indicates whether the metadata generated by the current resolver
        /// are compatible with the run time specified <see cref="JsonSerializerOptions"/>.
        /// </summary>
        internal static bool IsCompatibleWithOptions(this IJsonTypeInfoResolver? resolver, JsonSerializerOptions options)
            => resolver is IBuiltInJsonTypeInfoResolver bir && bir.IsCompatibleWithOptions(options);
    }

    /// <summary>
    /// A <see cref="IJsonTypeInfoResolver"/> that returns null for all inputs.
    /// </summary>
    internal sealed class EmptyJsonTypeInfoResolver : IJsonTypeInfoResolver, IBuiltInJsonTypeInfoResolver
    {
        public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => null;
        public bool IsCompatibleWithOptions(JsonSerializerOptions _) => true;
    }

    /// <summary>
    /// Implemented by the built-in converters to avoid rooting
    /// unused resolver dependencies in the context of the trimmer.
    /// </summary>
    internal interface IBuiltInJsonTypeInfoResolver
    {
        /// <summary>
        /// Indicates whether the metadata generated by the current resolver
        /// are compatible with the run time specified <see cref="JsonSerializerOptions"/>.
        /// </summary>
        bool IsCompatibleWithOptions(JsonSerializerOptions options);
    }
}
